/**
 * Author: Jackin (qin.zhang@uni.lu)
 * Date: 2013
 * (c) 2013 University of Luxembourg - Interdisciplinary Centre for Security Reliability and Trust (SnT)
 * All rights reserved
 * GPLv3 to be found at: http://www.gnu.org/licenses/quick-guide-gplv3.html
 */

package lu.uni.geko.ext.ram;

import lu.uni.geko.util.bridges.JavaBridge;
import lu.uni.geko.util.ecore.AbstractDeferringManuallyReferencingRecursivePkgVariantCopier;
import lu.uni.geko.util.ecore.AbstractRecursivePkgVariantEnumRespectingCopier;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;

/**
 * The copier is implemented by extending the original GeKo
 * AbstractDeferringManuallyReferencingRecursivePkgVariantCopier which leverage its EPackage variant
 * copying functions. However, regarding this RAM aspect copying case, we are going to make advice
 * variant from base model rather than to make base variant from advice model.
 * 
 * @author Jackin
 */
@SuppressWarnings("serial")
public class RamCopier extends AbstractDeferringManuallyReferencingRecursivePkgVariantCopier
{

	private String pkgSuffix;
	
	/**
	 * @param pkgSuffix The suffix to be appended to a package name
	 */
	public RamCopier(String pkgSuffix)
	{
		this.pkgSuffix = pkgSuffix;
	}

	@Deprecated
	public EObject getExistingVariant(EObject eObject)
	{
		return null;
	}

	@Deprecated
	public void registerVariant(EObject eObject, EObject variant)
	{

	}

	@Deprecated
	protected String getMMPackageNameSuffixToRemove()
	{
		return null;
	}

	/**
	 * Delegate {@link org.eclipse.emf.ecore.util.EcoreUtil.Copier#copy(EObject)
	 * EcoreUtil.Copier.copy(EObject)} to
	 * {@link AbstractRecursivePkgVariantEnumRespectingCopier#copyWithoutCheckingWhetherAlreadyCopied
	 * copyWithoutCheckingWhetherAlreadyCopied}.<br/>
	 * <br/>
	 * <b>Attention</b>: References have to be copied with an additional call to
	 * {@link org.eclipse.emf.ecore.util.EcoreUtil.Copier#copyReferences() copyReferences()}!
	 * 
	 * @param eObject
	 *          an eObject
	 * @return an existing or new variant of the given eObject
	 */
	@Override
	public EObject copy(final EObject eObject)
	{
		return copyWithoutCheckingWhetherAlreadyCopied(eObject);
	}

	/**
	 * @return the suffix that is to be appended to a metamodel package name to obtain the package
	 *         name of the variant
	 */
	private final String getMMPackageNameSuffixToAppend()
	{
		return this.pkgSuffix;
	}

	@Override
	protected EClass getTarget(final EClass eClass)
	{
		return getEClassByAppendingAPkgSuffix(eClass, getMMPackageNameSuffixToAppend());
	}

	@Override
	protected EStructuralFeature getTarget(final EStructuralFeature eStructuralFeature)
	{
		EClass variantEClass = eStructuralFeature.getEContainingClass();
		EClass baseEClass = getTarget(variantEClass);
		String featureName = eStructuralFeature.getName();
		EStructuralFeature baseFeature = baseEClass.getEStructuralFeature(featureName);
		return baseFeature;
	}

	/**
	 * Returns the metaclass variant corresponding to the given metaclass by appending the given
	 * suffix from the package name. (Modified based on the original method in the
	 * EcorePkgVariantsBridge.)
	 * 
	 * @param eClass
	 *          the original metaclass
	 * @param pkgNameSuffixToAppend
	 *          the suffix to be appended to the package name of the original metaclass
	 * @return the corresponding metaclass variant
	 */
	private EClass getEClassByAppendingAPkgSuffix(final EClass eClass, final String pkgNameSuffixToAppend)
	{
		EClassifier classifierVariant = getEClassifierByAppendingAPkgSuffix(eClass, pkgNameSuffixToAppend);
		return JavaBridge.dynamicCast(classifierVariant, EClass.class, "classifier variant");
	}

	/**
	 * Returns the classifier variant corresponding to the given classifier by removing the given
	 * suffix from the package name. (Modified based on the original method in the
	 * EcorePkgVariantsBridge.)
	 * 
	 * @param eClassifier
	 *          the original classifier
	 * @param pkgNameSuffixToAppend
	 *          the suffix to be appended to the package name of the original classifier
	 * @return the corresponding classifier variant
	 */
	private EClassifier getEClassifierByAppendingAPkgSuffix(final EClassifier eClassifier, final String pkgNameSuffixToAppend)
	{
		String originalPackageNsURI = eClassifier.getEPackage().getNsURI();
		String variantPackageNsURI = appendSuffixToPkgName(originalPackageNsURI, pkgNameSuffixToAppend);
		return getEClassifierInPkgVariant(eClassifier, variantPackageNsURI);
	}

	/**
	 * Returns the classifier with the same name as the given classifier from the package variant
	 * using the given package namespace URI.
	 * (Modified based on the original method in the EcorePkgVariantsBridge.)
	 * 
	 * @param eClassifier
	 *          a classifier of the package for the given namespace URI
	 * @param pkgVariantNsURI
	 *          the namespace URI of the package containing a classifier with the given name
	 * @return the classifier variant
	 */
	private EClassifier getEClassifierInPkgVariant(final EClassifier eClassifier, final String pkgVariantNsURI)
	{
		EPackage pkgVariant = EPackage.Registry.INSTANCE.getEPackage(pkgVariantNsURI);
		String className = eClassifier.getName();
		return pkgVariant.getEClassifier(className);
	}

	/**
	 * Append the given package name suffix to the given package name and returns the result.
	 * (Modified based on the method in JavaPkgNameBridge)
	 * 
	 * @param pkgName
	 *          a package name
	 * @param pkgNameSuffixToAppend
	 *          the package name suffix to be append
	 * @return the trimmed package name
	 */
	private String appendSuffixToPkgName(final String pkgName, final String pkgNameSuffixToAppend)
	{
		String[] splitAndTrimmedClassName = splitAndAppendSuffixToPkgName(pkgName, pkgNameSuffixToAppend);
		String trimmedClassName = splitAndTrimmedClassName[0];
		for (int i = 1; i < splitAndTrimmedClassName.length; i++)
		{
			trimmedClassName += "." + splitAndTrimmedClassName[i];
		}
		return trimmedClassName;
	}

	/**
	 * Splits the given package name at every occurrence of a dot ".", append the given package name
	 * suffix to the last segment and returns the modified splitting array.
	 * (Modified based on the method in JavaPkgNameBridge)
	 * 
	 * @param pkgName
	 *          a package name
	 * @param pkgNameSuffixToAppend
	 *          the package name suffix to be appended
	 * @return the array resulting from splitting and replacing
	 */
	private String[] splitAndAppendSuffixToPkgName(final String pkgName, final String pkgNameSuffixToAppend)
	{
		String[] splitPkgName = pkgName.split("\\.");
		if (splitPkgName.length > 0)
		{
			int canonicalPkgNameIndex = splitPkgName.length - 1;
			String canonicalPkgName = splitPkgName[canonicalPkgNameIndex];
			String trimmedPkgName = canonicalPkgName + pkgNameSuffixToAppend;
			splitPkgName[canonicalPkgNameIndex] = trimmedPkgName;
			return splitPkgName;
		} else
		{
			throw new RuntimeException("Cannot append the pkg name suffix '" + pkgNameSuffixToAppend + "' to the package name '" + pkgName
					+ "' as it does not have a package!");
		}
	}

}
